//
//  CLNetWorker.m
//  Charles
//
//  Created by Charles on 2018/6/6.
//  Copyright © 2018年 charles. All rights reserved.
//

#import "CLNetWorkManager.h"
#import "CLNetWorkConfig.h"
#import "CLNetWorkPrivate.h"
#if __has_include(<AFNetworking/AFNetworking.h>)
#import <AFNetworking/AFNetworking.h>
#else
#import "AFNetworking.h"
#endif
#import <pthread/pthread.h>

#import "CLBaseRequest.h"

#define Lock() pthread_mutex_lock(&_lock)
#define Unlock() pthread_mutex_unlock(&_lock)

#define kCLNetworkIncompleteDownloadFolderName @"Incomplete"

@implementation CLNetWorkManager {
    CLNetWorkConfig *_config;
    AFHTTPSessionManager *_manager;
    AFJSONResponseSerializer *_jsonResponseSerializer;
    AFXMLParserResponseSerializer *_xmlResponseSerializer;
    NSMutableDictionary<NSNumber *,CLBaseRequest *> *_requestsRecord;
    
    dispatch_queue_t _processingQueue;
    pthread_mutex_t _lock;
    NSIndexSet *_allStatusCodes;
}

+ (CLNetWorkManager *)sharedManager {
    static id sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc]init];
    });
    return sharedInstance;
}

- (instancetype)init{
    if (self = [super init]) {
        _config = [CLNetWorkConfig sharedConfig];
        _manager = [[AFHTTPSessionManager alloc]initWithSessionConfiguration:_config.sessionConfiguration];
        _requestsRecord = [NSMutableDictionary dictionary];
        _processingQueue = dispatch_queue_create("com.charles.leo.processing", DISPATCH_QUEUE_CONCURRENT);
        _allStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(100, 500)];
        pthread_mutex_init(&_lock, NULL);
        
        _manager.securityPolicy = _config.securityPolicy;
        _manager.responseSerializer = [AFHTTPResponseSerializer serializer];
        
        _manager.responseSerializer.acceptableStatusCodes = _allStatusCodes;
        _manager.completionQueue = _processingQueue;
    }
    return self;
}

- (AFJSONResponseSerializer *)jsonResponseSerializer {
    if (!_jsonResponseSerializer) {
        _jsonResponseSerializer = [AFJSONResponseSerializer serializer];
        _jsonResponseSerializer.acceptableStatusCodes = _allStatusCodes;
    }
    return _jsonResponseSerializer;
}

- (AFXMLParserResponseSerializer *)xmlResponseSerializer {
    if (!_xmlResponseSerializer) {
        _xmlResponseSerializer = [AFXMLParserResponseSerializer serializer];
        _xmlResponseSerializer.acceptableStatusCodes = _allStatusCodes;
    }
    return _xmlResponseSerializer;
}

#pragma mark -
// 返回当前请求url
- (NSString *)buildRequestUrl:(CLBaseRequest *)request {
    NSParameterAssert(request != nil);
    // 用户自定义的url(不包括CLNetWorkConfig里面设置的baseUrl)
    NSString *detailUrl = [request requestUrl];
    // 存在host和scheme的url立即返回正确
    NSURL *tempUrl = [NSURL URLWithString:detailUrl];
    if (tempUrl && tempUrl.host && tempUrl.scheme) {
        return detailUrl;
    }
    // 如果需要过滤url,则过滤
    NSArray *filters = [_config urlFilters];
    for (id<CLUrlFilterProtocol>filter in filters) {
        detailUrl = [filter filterUrl:detailUrl withRequest:request];
    }
    // 如果使用baseUrl，在当前请求没有配置baseUrl,返回全局配置的baseUrl
    NSString *baseUrl = nil;
    if ([request baseUrl].length > 0) {
        baseUrl = [request baseUrl];
    }else {
        baseUrl = [_config baseUrl];
    }
    // 如果末尾没有/,则在末尾添加/
    NSURL *url = [NSURL URLWithString:baseUrl];
    if (baseUrl.length > 0 && ![baseUrl hasSuffix:@"/"]) {
        url = [url URLByAppendingPathComponent:@""];
    }
    return  [NSURL URLWithString:detailUrl relativeToURL:url].absoluteString;
}

- (AFHTTPRequestSerializer *)requestSerializerForRequest:(CLBaseRequest *)request {
    AFHTTPRequestSerializer *requestSerializer = nil;
    if (request.requestSerializerType == CLRequestSerializerTypeHTTP) {
        requestSerializer = [AFHTTPRequestSerializer serializer];
    }else if (request.requestSerializerType == CLRequestSerializerTypeJSON){
        requestSerializer = [AFJSONRequestSerializer serializer];
    }
    
    requestSerializer.timeoutInterval = [request requestTimeoutInterval];
    requestSerializer.allowsCellularAccess = [request allowsCellularAccess];
    NSArray<NSString *> *authorizationHeaderFiledArray = [request requestAuthorizationHeaderFieldArray];
    if (authorizationHeaderFiledArray != nil) {
        [requestSerializer setAuthorizationHeaderFieldWithUsername:authorizationHeaderFiledArray.firstObject password:authorizationHeaderFiledArray.lastObject];
    }
    NSDictionary<NSString *, NSString *> *headerFieldValueDictionay = [request requestHeaderFieldValueDictionary];
    if (headerFieldValueDictionay != nil) {
        for (NSString *httpHeaderField in headerFieldValueDictionay.allKeys) {
            NSString *value = headerFieldValueDictionay[httpHeaderField];
            [requestSerializer setValue:value forHTTPHeaderField:httpHeaderField];
        }
    }
    return requestSerializer;
}

// 根据不同请求类型，序列化类型，和请求参数来返回NSURLSessionTask
- (NSURLSessionTask *)sessionTaskForRequest:(CLBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
    // 获取请求类型
    CLRequestMethodType method = [request requestMethod];
    // 获取请求url
    NSString *url = [self buildRequestUrl:request];
    // 获取请求参数
    id param = request.requestArguments;
    AFConstructingBlock constructingBlock = [request constructingBodyBlock];
    // 获取request serializer
    AFHTTPRequestSerializer *requestSerializer = [self requestSerializerForRequest:request];
    // 根据不同的请求类型来返回对应的task
    if (CLRequestMethodTypeGET == method) {
        if (request.resumableDownloadPath) {
            // 下载任务
            return [self downloadTaskWithDownloadPath:request.resumableDownloadPath requestSerializer:requestSerializer URLString:url parameters:param progress:request.resumableDownloadProgressBlock error:error];
        }else {
            // 普通get请求
            return [self dataTaskWithHTTPMethod:@"GET" requestSerializer:requestSerializer URLString:url parameter:param error:error];
        }
    }else if (CLRequestMethodTypePOST == method){
        return [self dataTaskWithHTTPMethod:@"POST" requestSerializer:requestSerializer URLString:url parameter:param constructingBodyWithBlock:constructingBlock error:error];

    }
    return nil;
}

- (void)addRequest:(CLBaseRequest *)request {
    // 获取task
    NSParameterAssert(request != nil);
    NSError *__autoreleasing requestSerializationError = nil;
    // 获取用户自定义的requestUrl
    NSURLRequest *customUrlRequest = [request buildCustomUrlRequest];
    if (customUrlRequest) {
        // 如果用户自定义的request存在，则直接走AFNetWorking的dataTaskWithRequest:方法
        __block NSURLSessionDataTask *dataTask = nil;
        dataTask = [_manager dataTaskWithRequest:customUrlRequest completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
            // 响应的统一处理
            [self handleRequestResult:dataTask responseObject:responseObject error:error];
        }];
        request.requestTask = dataTask;
    }else {
        // 如果用户没有自定义request，则直接走这里
        request.requestTask = [self sessionTaskForRequest:request error:&requestSerializationError];
    }
    // 如果序列化失败，则认为请求失败
    if (requestSerializationError) {
        // 请求失败的处理
        [self requestDidFailWithRequest:request error:requestSerializationError];
        return;
    }
    
    NSAssert(request.requestTask != nil, @"requstTask should not be nil");
    // 请求优先级映射
    if ([request.requestTask respondsToSelector:@selector(priority)]) {
        switch (request.requestPriority) {
            case CLRequestPriorityHigh:
                request.requestTask.priority = NSURLSessionTaskPriorityHigh;
                break;
            case CLRequestPriorityLow:
                request.requestTask.priority = NSURLSessionTaskPriorityLow;
                break;
            case CLRequestPriorityDefault:
            default:
                request.requestTask.priority = NSURLSessionTaskPriorityDefault;
                break;
        }
    }
    NSLog(@"Add requst: %@",NSStringFromClass([request class]));
    // 将request放入保存请求的字典中，taskIndentifier为key,request为值
    [self addRequestToRecord:request];
    // 开始task
    [request.requestTask resume];
}

// 取消某个request
- (void)cancelRequest:(CLBaseRequest *)request {
    NSParameterAssert(request != nil);
    if (request.resumableDownloadPath) {
        NSURLSessionDownloadTask *requestTask = (NSURLSessionDownloadTask *)request.requestTask;
        [requestTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
            NSURL *localUrl = [self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath];
            [resumeData writeToURL:localUrl atomically:YES];
        }];
    } else {
        [request.requestTask cancel];
    }
    [self removeRequestFromRecord:request];
    [request clearCompletionBlock];
}

// 取消所有在字典里添加的request
- (void)cancelAllRequests {
    Lock();
    NSArray *allKeys = [_requestsRecord allKeys];
    Unlock();
    if (allKeys && allKeys.count > 0) {
        NSArray *copiedKeys = [allKeys copy];
        for (NSNumber *key in copiedKeys) {
            Lock();
            CLBaseRequest *request = _requestsRecord[key];
            Unlock();
            [request stop];
        }
    }
}

// 把request添加到专门用来保存请求的字典中 key 为 taskIdentifier
- (void)addRequestToRecord:(CLBaseRequest *)request {
    Lock();
    _requestsRecord[@(request.requestTask.taskIdentifier)] = request;
    Unlock();
}

// 从字典里移除某request
- (void)removeRequestFromRecord:(CLBaseRequest *)request {
    Lock();
    [_requestsRecord removeObjectForKey:@(request.requestTask.taskIdentifier)];
    NSLog(@"Request queue size = %zd", [_requestsRecord count]);
    Unlock();
}

// 判断Code是否符合范围和json的有效性
- (BOOL)validateResult:(CLBaseRequest *)request error:(NSError * _Nullable __autoreleasing *)error {
    return NO;
}

// 统一处理请求结果，包括成功和失败的情况
- (void)handleRequestResult:(NSURLSessionTask *)task responseObject:(id)responseObject error:(NSError *)error {
    // 获取task对应的request
    Lock();
    CLBaseRequest *request = _requestsRecord[@(task.taskIdentifier)];
    Unlock();
    // 如果不存在对应的request立即返回
    if (!request) {
        return;
    }
    NSLog(@"Finished Request:%@",NSStringFromClass([request class]));
    NSError *__autoreleasing serializationError = nil;
    NSError *__autoreleasing validationError = nil;
    
    NSError *requestError = nil;
    BOOL succeed = NO;
    // 获取request对应的response
    request.responseObject = responseObject;
    
    // 获取responseObject,responseData和responseString
    if ([request.responseObject isKindOfClass:[NSData class]]) {
        // 获取responseData
        request.responseData = responseObject;
        // 获取responseString
        request.responseString = [[NSString alloc]initWithData:responseObject encoding:[CLNetWorkUtils stringEncodingWithRequest:request]];
        // 获取responseObject
        // 根据返回的响应的序列化的类型来得到对应类型的响应
        switch (request.responseSerializerType) {
            case CLResponseSerializerTypeHTTP:
                
                break;
            case CLResponseSerializerTypeJSON:
                request.responseObject = [self.jsonResponseSerializer responseObjectForResponse:task.response data:request.responseData error:&serializationError];
                request.responseJSONObject = request.responseObject;
                break;
            case CLResponseSerializerTypeXMLParser:
                request.responseObject = [self.xmlResponseSerializer responseObjectForResponse:task.response data:request.responseData error:&serializationError];
                break;

            default:
                break;
        }
    }
    // 判断是否有错误，将错误对象赋值给requestError，改变succeed的布尔值。由此来判断到底是进行成功回调，还是失败回调。
    if (error) {
        // 如果方法传入的error不为nil
        succeed = NO;
        requestError = error;
    }else if (serializationError) {
        // 如果序列化失败了
        succeed = NO;
        requestError = serializationError;
    }else {
        // 即使没有error而且序列化通过，也要验证request是否有效
        succeed = [self validateResult:request error:&validationError];
        requestError = validationError;
    }
    // 根据succeed的布尔值来调用相应的处理
    if (succeed) {
        // 请求成功的处理
        [self requestDidSucceedWithRequest:request];
    }else {
        // 请求失败的处理
        [self requestDidFailWithRequest:request error:requestError];
    }
    // 回调完成的处理
    dispatch_async(dispatch_get_main_queue(), ^{
        // 在字典中移除当前的request
        [self removeRequestFromRecord:request];
        // 清楚所有block
        [request clearCompletionBlock];
    });
    
}

- (void)requestDidSucceedWithRequest:(CLBaseRequest *)request {
    @autoreleasepool {
        [request requestCompletePreprocessor];
    }
    dispatch_async(dispatch_get_main_queue(), ^{
        [request requestCompleteFilter];
        if (request.delegate != nil) {
            [request.delegate requestFinished:request];
        }
        if (request.successCompletionBlock) {
            request.successCompletionBlock(request);
        }
    });
}

- (void)requestDidFailWithRequest:(CLBaseRequest *)request error:(NSError *)error {
    request.error = error;
    NSData *incompleteDownloadData = error.userInfo[NSURLSessionDownloadTaskResumeData];
    if (incompleteDownloadData) {
        [incompleteDownloadData writeToURL:[self incompleteDownloadTempPathForDownloadPath:request.resumableDownloadPath] atomically:YES];
    }
    if ([request.responseObject isKindOfClass:[NSURL class]]) {
        NSURL *url = request.responseObject;
        if (url.isFileURL && [[NSFileManager defaultManager]fileExistsAtPath:url.path]) {
            request.responseData = [NSData dataWithContentsOfURL:url];
            request.responseString = [[NSString alloc]initWithData:request.responseData encoding:[CLNetWorkUtils stringEncodingWithRequest:request]];
            [[NSFileManager defaultManager]removeItemAtURL:url error:nil];
        }
        request.responseObject = nil;
    }
    
    @autoreleasepool {
        [request requestFailedPreprocessor];
    }
    
    dispatch_async(dispatch_get_main_queue(), ^{
        [request requestFailedFilter];
        if (request.delegate != nil) {
            [request.delegate requestFailed:request];
        }
        if (request.failureCompletionBlock) {
            request.failureCompletionBlock(request);
        }
    });
}

#pragma mark -

- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                               requestSerializer:(AFHTTPRequestSerializer *)requestSerializer
                                       URLString:(NSString *)URLString parameter:(id)parameters
                                           error:(NSError * _Nullable __autoreleasing *)error {
    return [self dataTaskWithHTTPMethod:method requestSerializer:requestSerializer URLString:URLString parameter:parameters constructingBodyWithBlock:nil error:error];
}

- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                               requestSerializer:(AFHTTPRequestSerializer *)requestSerializer
                                       URLString:(NSString *)URLString parameter:(id)parameters constructingBodyWithBlock:(void(^)(id<AFMultipartFormData> formData))block
                                           error:(NSError * _Nullable __autoreleasing *)error {
    NSMutableURLRequest *request = nil;
    if (block) {
        request = [requestSerializer multipartFormRequestWithMethod:method URLString:URLString parameters:parameters constructingBodyWithBlock:block error:error];
    }else {
        request = [requestSerializer requestWithMethod:method URLString:URLString parameters:parameters error:error];
    }
    __block NSURLSessionDataTask *dataTask = nil;
    dataTask = [_manager dataTaskWithRequest:request completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {
        [self handleRequestResult:dataTask responseObject:responseObject error:error];
    }];
    return dataTask;
}

- (NSURLSessionDownloadTask *)downloadTaskWithDownloadPath:(NSString *)downloadPath
                                         requestSerializer:(AFHTTPRequestSerializer *)requestSerializer
                                                 URLString:(NSString *)URLString
                                                parameters:(id)parameters
                                                  progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock
                                                     error:(NSError * _Nullable __autoreleasing *)error {
    NSMutableURLRequest *urlRequest = [requestSerializer requestWithMethod:@"GET" URLString:URLString parameters:parameters error:error];
    NSString *downloadTargetPath;
    BOOL isDirectory;
    if (![[NSFileManager defaultManager]fileExistsAtPath:downloadPath isDirectory:&isDirectory]) {
        isDirectory = NO;
    }
    if (isDirectory) {
        NSString *fileName = [urlRequest.URL lastPathComponent];
        downloadTargetPath = [NSString pathWithComponents:@[downloadPath, fileName]];
    }else {
        downloadTargetPath = downloadPath;
    }
    if ([[NSFileManager defaultManager]fileExistsAtPath:downloadPath]) {
        [[NSFileManager defaultManager]removeItemAtPath:downloadTargetPath error:nil];
    }

    
    BOOL resumeDataFileExists = [[NSFileManager defaultManager]fileExistsAtPath:[self incompleteDownloadTempPathForDownloadPath:downloadPath].path];
    NSData *data = [NSData dataWithContentsOfURL:[self incompleteDownloadTempPathForDownloadPath:downloadPath]];
    BOOL resumeDataIsValid = [CLNetWorkUtils validateResumeData:data];
    BOOL canBeResumed = resumeDataFileExists && resumeDataIsValid;
    BOOL resumeSucceeded = NO;
    __block NSURLSessionDownloadTask *downloadTask = nil;
    if (canBeResumed){
        @try {
            downloadTask = [_manager downloadTaskWithResumeData:data progress:downloadProgressBlock destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
                return [NSURL fileURLWithPath:downloadTargetPath isDirectory:NO];
            } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
                [self handleRequestResult:downloadTask responseObject:filePath error:error];
            }];
            resumeSucceeded = YES;
        }@catch(NSException *exception) {
            NSLog(@"Resume download failed, reason = %@",exception.reason);
            resumeSucceeded = NO;
        }
    }
    if (!resumeSucceeded) {
        downloadTask = [_manager downloadTaskWithRequest:urlRequest progress:downloadProgressBlock destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
            return [NSURL fileURLWithPath:downloadTargetPath isDirectory:NO];
        } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
            [self handleRequestResult:downloadTask responseObject:filePath error:error];
        }];
    }
    return downloadTask;
}

#pragma mark - Resumable Download

- (NSString *)incompleteDownloadTempCacheFolder {
    NSFileManager *fileManager = [NSFileManager new];
    static NSString *cacheFolder;
    if (!cacheFolder) {
        NSString *cacheDir = NSTemporaryDirectory();
        cacheFolder = [cacheDir stringByAppendingPathComponent:kCLNetworkIncompleteDownloadFolderName];
    }
    NSError *error = nil;
    if ([fileManager createDirectoryAtPath:cacheFolder withIntermediateDirectories:YES attributes:nil error:&error]) {
        NSLog(@"Failed to create cache directory at %@",cacheFolder);
        cacheFolder = nil;
    }
    return cacheFolder;
}

- (NSURL *)incompleteDownloadTempPathForDownloadPath:(NSString *)downloadPath {
    NSString *tempPath = nil;
    NSString *md5URLString = [CLNetWorkUtils md5StringFromString:downloadPath];
    tempPath = [[self incompleteDownloadTempCacheFolder] stringByAppendingPathComponent:md5URLString];
    return [NSURL fileURLWithPath:tempPath];
}

@end
